package raft

import (
	"fmt"
	"sync"
)

//日志结构体
type LogEntry struct {
	// 日志任期号
	Term int
	// 日志命令
	Cmd string
}

//日志复制结构体
type LogReplicate struct {
	PreLogIndex int
	PreLogTerm  int
	LogSlc      []LogEntry
	//Leader节点的Node
	LeaderNode Node
	//Leader提交的日志索引值  --暂未使用
	LeaderCommitIndex int
}

//日志复制响应
type LogReplicateReply struct {
	//一致性结果： true：复制成功
	Result bool
	//日志复制失败时，冲突的日志索引值 -1：代表日志的索引没有找到-》需要不断的往后倒
	ConflictLogIdx int
	//节点id
	RemoteNode Node
}

/*
  初始化所有节点的NextIndex值
*/
func (this *Raft) resetAllNodeNextIndex() {
	nextLogIdx := this.logIndex + 1
	if this.logIndex == 0 {
		nextLogIdx = 0
	}
	for _, node := range this.allNodeSlc {
		this.nodeIdKeyNextIndexMap[node.Id] = nextLogIdx
	}
}

/**
  发送日志一致性检查
*/
func (this *Raft) SendLogConsistencyCheck() {
	for _, remoteNode := range this.allNodeSlc {
		if remoteNode.Id != this.selfNodeId {
			go func(remoteNode Node) {
				logReplicate := LogReplicate{
					PreLogIndex: this.nodeIdKeyNextIndexMap[remoteNode.Id],
					PreLogTerm:  this.Term,
					LeaderNode:  this.selfNode,
				}
				logReplicateReplyPtr := &LogReplicateReply{}
				Call(remoteNode, "Raft.ConsistencyCheckDaemonRpcCall", logReplicate, logReplicateReplyPtr)
				fmt.Printf("%s一致性检查返回的值:%v\n", remoteNode.Addr, logReplicateReplyPtr)
				this.Lock()
				if logReplicateReplyPtr.Result == false {
					remoteNodeId := logReplicateReplyPtr.RemoteNode.Id
					if logReplicateReplyPtr.ConflictLogIdx != -1 {
						//找到日志索引冲突的处理
						this.nodeIdKeyNextIndexMap[remoteNodeId] = logReplicateReplyPtr.ConflictLogIdx
					} else {
						//没有找到冲突索引的处理
						this.nodeIdKeyNextIndexMap[remoteNodeId] = this.nodeIdKeyNextIndexMap[remoteNodeId] - 1
					}
				}
				this.Unlock()
			}(remoteNode)
		}
	}
}

/**
发送日志复制
*/
func (this *Raft) sendLogCopy(logEntry LogEntry) bool {
	var preLogIndex int
	var preLogTerm int
	if len(this.LogSlc) > 0 {
		preLogIndex = len(this.LogSlc) - 1
		preLogTerm = this.LogSlc[preLogIndex].Term
	} else {
		preLogIndex = 0
		preLogTerm = this.Term
	}
	this.logIndex++
	this.LogSlc = append(this.LogSlc, logEntry)
	var wg sync.WaitGroup
	var logCopyReplyChn = make(chan *LogReplicateReply, len(this.allNodeSlc)-1)
	for _, remoteNode := range this.allNodeSlc {
		if remoteNode.Id != this.selfNodeId {
			wg.Add(1)
			go func(preLogIndex int, preLogTerm int, remoteNode Node) {
				//需要复制的日志
				sendLogSlc := this.LogSlc[this.nodeIdKeyNextIndexMap[remoteNode.Id]:]
				logCopy := LogReplicate{
					PreLogIndex: preLogIndex,
					PreLogTerm:  preLogTerm,
					LogSlc:      sendLogSlc,
				}
				logCopyReplyPtr := &LogReplicateReply{}
				Call(remoteNode, "Raft.AppendEntryRpcCall", logCopy, logCopyReplyPtr)
				logCopyReplyChn <- logCopyReplyPtr
				wg.Done()
			}(preLogIndex, preLogTerm, remoteNode)
		}
	}
	wg.Wait()
	var copySucNum = 1
	close(logCopyReplyChn)
	for logCopyReplyPtr := range logCopyReplyChn {
		remoteNodeId := logCopyReplyPtr.RemoteNode.Id
		if logCopyReplyPtr.Result {
			copySucNum++
			this.nodeIdKeyNextIndexMap[remoteNodeId] = this.logIndex
			fmt.Printf(logEntry.Cmd+"日志复制后，节点返回的冲突检验参数：%v\n", logCopyReplyPtr)
		} else {
			if logCopyReplyPtr.ConflictLogIdx == -1 {
				//没有找到对应的logIndex和term的日志
				//对应节点的nextIndex值为logIndex-1
				this.nodeIdKeyNextIndexMap[remoteNodeId] = this.nodeIdKeyNextIndexMap[remoteNodeId] - 1
				this.nodeIdKeyCopiedIndexMap[remoteNodeId] = -1
				fmt.Printf("没有找到冲突校验值,%v\n", logCopyReplyPtr)
			} else {
				//存在冲突，找到了冲突的索引值
				this.nodeIdKeyNextIndexMap[remoteNodeId] = logCopyReplyPtr.ConflictLogIdx + 1
				this.nodeIdKeyCopiedIndexMap[remoteNodeId] = logCopyReplyPtr.ConflictLogIdx
				fmt.Printf(logEntry.Cmd+"日志复制后，节点返回的冲突检验参数：%v\n", logCopyReplyPtr)
			}
		}
	}
	if copySucNum >= len(this.allNodeSlc)/2 {
		this.commitIndex = this.logIndex
		fmt.Println(logEntry.Cmd + "发送成功，已提交！")
		return true
	} else {
		fmt.Println(logEntry.Cmd + "发送失败，未提交！")
		return false
	}
}

/**
日志一致性校验
*/
func (this *Raft) consistencyCheckDaemon(logReplicate LogReplicate, logReplicateReplyPtr *LogReplicateReply) {
	if len(this.LogSlc) == 0 {
		//当前节点没有日志的情况
		if logReplicate.PreLogIndex == 0 {
			//Leader节点也没有日志的情况
			logReplicateReplyPtr.Result = true
			logReplicateReplyPtr.ConflictLogIdx = -1
			logReplicateReplyPtr.RemoteNode = this.selfNode
			//fmt.Println("当前节点没有日志，Leader也没有日志的一致性校验！")
		} else {
			//Leader节点有日志的情况
			logReplicateReplyPtr.Result = false
			logReplicateReplyPtr.ConflictLogIdx = 0
			logReplicateReplyPtr.RemoteNode = this.selfNode
			//fmt.Printf("当前节点没有日志，Leader有日志的一致性校验！%v\n",logReplicateReplyPtr)
		}
	} else {
		//当前节点有日志的情况
		lastLog := this.LogSlc[len(this.LogSlc)-1]
		logReplicateReplyPtr.RemoteNode = this.selfNode
		logTerm := lastLog.Term
		if logTerm == 0 {
			logTerm = 1
		}
		if this.logIndex == logReplicate.PreLogIndex && logTerm == logReplicate.PreLogTerm {
			logReplicateReplyPtr.Result = true
			logReplicateReplyPtr.ConflictLogIdx = -1
			fmt.Printf("prelogIndex和节点的logIndex一致，一致性检查的结果%v\n", logReplicateReplyPtr)
		} else {
			//日志不一致的冲突校验
			var logExistFlag bool
			for i := len(this.LogSlc) - 1; i >= 0; i-- {
				if this.logIndex == logReplicate.PreLogIndex && this.LogSlc[i].Term == logReplicate.PreLogTerm {
					logExistFlag = true
					logReplicateReplyPtr.ConflictLogIdx = logReplicate.PreLogIndex
					break
				}
			}
			logReplicateReplyPtr.Result = false
			if !logExistFlag {
				//当前日志不存在
				logReplicateReplyPtr.ConflictLogIdx = -1
			}
			fmt.Printf("日志不一致，传递的参数：%v,一致性检查的结果%v\n", logReplicate, logReplicateReplyPtr)
		}
	}
}

/*
 日志追加
*/
func (this *Raft) appendLog(logReplicate LogReplicate, logReplicateReplyPtr *LogReplicateReply) {
	if len(logReplicate.LogSlc) == 0 {
		//Leader也没有日志
		return
	}
	if logReplicateReplyPtr.Result {
		this.LogSlc = append(this.LogSlc, logReplicate.LogSlc...)
		fmt.Printf("日志一致，拷贝日志：%v\n", logReplicate)
		fmt.Printf("当前节点日志：%v\n", this.LogSlc)
		this.logIndex = len(this.LogSlc) - 1
	} else if logReplicateReplyPtr.ConflictLogIdx != -1 {
		//找到日志冲突点，拷贝日志
		if logReplicate.PreLogIndex == logReplicateReplyPtr.ConflictLogIdx {
			//已经做过日志一致性校验，直接复制日志切片
			this.LogSlc = append(this.LogSlc[0:logReplicate.PreLogIndex+1], logReplicate.LogSlc...)
			logReplicateReplyPtr.ConflictLogIdx = logReplicateReplyPtr.ConflictLogIdx + len(logReplicate.LogSlc) - 1
			this.logIndex = len(this.LogSlc) - 1
			fmt.Printf("日志不一致，preLogIndex和conflictLogIdx一致，拷贝日志：%v,拷贝后的返回值：%v\n", logReplicate, logReplicateReplyPtr)
			fmt.Printf("当前节点日志：%v\n", this.LogSlc)
		}
	}
}
